package com.onadake.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

import org.apache.commons.lang3.time.DateUtils;

/**
 * Class with utility methods for work with dates and times.
 * 
 * @author radek.hecl
 * 
 */
public class Dates {

    /**
     * Enumerates the months of the year.
     * 
     * @author radek.hecl
     * 
     */
    public static enum Month {
        JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER
    }

    /**
     * Implementation of dates provider.
     */
    private static DatesProvider provider = new DatesProvider() {
        
        @Override
        public Date yesterday() {
            return Dates.yesterday();
        }
        
        @Override
        public Date today() {
            return Dates.today();
        }
        
        @Override
        public Date now() {
            return Dates.now();
        }
    };
    
    /**
     * Prevents construction from outside.
     */
    private Dates() {
    }
    
    /**
     * Returns the current timestamp.
     * 
     * @return current timestamp
     */
    public static Date now() {
        return new Date();
    }

    /**
     * Returns todays date. This is current timestamp which is truncated to
     * days.
     * 
     * @return todays date
     */
    public static Date today() {
        return DateUtils.truncate(new Date(), Calendar.DATE);
    }

    /**
     * Returns the today's date in the specified time zone.
     * Example: JVM local time zone is JST (Japan Standard Time) and provided time zone is PST (Pacific Standard Time).
     * Result will be date truncated to the day in JST (as all java.util.Date are in local time zone).
     * But as PST is 17 hours behind the JST, the returned date can be today or yesterday. 
     * 
     * @param timezone time zone to use
     * @return created date
     */
    public static Date todayWithSchiftedTimeZone(TimeZone timezone) {
        Calendar calendar = Calendar.getInstance(timezone);
        Month month = null;
        switch (calendar.get(Calendar.MONTH)) {
        case 0:
            month = Month.JANUARY;
            break;
        case 1:
            month = Month.FEBRUARY;
            break;
        case 2:
            month = Month.MARCH;
            break;
        case 3:
            month = Month.APRIL;
            break;
        case 4:
            month = Month.MAY;
            break;
        case 5:
            month = Month.JUNE;
            break;
        case 6:
            month = Month.JULY;
            break;
        case 7:
            month = Month.AUGUST;
            break;
        case 8:
            month = Month.SEPTEMBER;
            break;
        case 9:
            month = Month.OCTOBER;
            break;
        case 10:
            month = Month.NOVEMBER;
            break;
        case 11:
            month = Month.DECEMBER;
            break;
        default:
            throw new IllegalArgumentException("unsupported month: calendar = " + calendar);
        }
        return Dates.createDate(calendar.get(Calendar.YEAR), month, calendar.get(Calendar.DAY_OF_MONTH));
    }
    
    /**
     * Returns the yesterdays date. This is truncated to date.
     * 
     * @return yesterdays date
     */
    public static Date yesterday() {
        return DateUtils.addDays(today(), -1);
    }

    /**
     * Returns the tomorrows date. This is truncated to date.
     * 
     * @return tomorrows date
     */
    public static Date tomorrow() {
        return DateUtils.addDays(today(), 1);
    }

    /**
     * Returns the date which is shifted for a particular number of days from
     * today. This is truncated to date.
     * 
     * @param numDays the number of days to shift
     * @return date shifted from today
     */
    public static Date fromToday(int numDays) {
        return DateUtils.addDays(today(), numDays);
    }

    /**
     * Creates date according the Gregorian calendar input.
     * 
     * @param year year
     * @param month month
     * @param day day of the month (1-31, range depends on the month and year)
     * @return created date
     */
    public static Date createDate(int year, Month month, int day) {
        Calendar calendar = new GregorianCalendar();
        calendar.clear();
        calendar.set(Calendar.YEAR, year);
        switch (month) {
        case JANUARY:
            calendar.set(Calendar.MONTH, 0);
            break;
        case FEBRUARY:
            calendar.set(Calendar.MONTH, 1);
            break;
        case MARCH:
            calendar.set(Calendar.MONTH, 2);
            break;
        case APRIL:
            calendar.set(Calendar.MONTH, 3);
            break;
        case MAY:
            calendar.set(Calendar.MONTH, 4);
            break;
        case JUNE:
            calendar.set(Calendar.MONTH, 5);
            break;
        case JULY:
            calendar.set(Calendar.MONTH, 6);
            break;
        case AUGUST:
            calendar.set(Calendar.MONTH, 7);
            break;
        case SEPTEMBER:
            calendar.set(Calendar.MONTH, 8);
            break;
        case OCTOBER:
            calendar.set(Calendar.MONTH, 9);
            break;
        case NOVEMBER:
            calendar.set(Calendar.MONTH, 10);
            break;
        case DECEMBER:
            calendar.set(Calendar.MONTH, 11);
            break;
        default:
            throw new IllegalArgumentException("unknown month: month = " + month);
        }
        calendar.set(Calendar.DATE, day);
        return calendar.getTime();
    }

    /**
     * Creates time according the Gregorian calendar input
     * 
     * @param year year
     * @param month month
     * @param day day of the month (1-31, range depends on the month and year)
     * @param hour hour of the day (0 - 23)
     * @param minute minute of the day (0 - 59)
     * @param second second of the minute (0 - 59)
     * @return created time
     */
    public static Date createTime(int year, Month month, int day, int hour, int minute, int second) {
        Calendar calendar = new GregorianCalendar();
        calendar.clear();
        calendar.set(Calendar.YEAR, year);
        switch (month) {
        case JANUARY:
            calendar.set(Calendar.MONTH, 0);
            break;
        case FEBRUARY:
            calendar.set(Calendar.MONTH, 1);
            break;
        case MARCH:
            calendar.set(Calendar.MONTH, 2);
            break;
        case APRIL:
            calendar.set(Calendar.MONTH, 3);
            break;
        case MAY:
            calendar.set(Calendar.MONTH, 4);
            break;
        case JUNE:
            calendar.set(Calendar.MONTH, 5);
            break;
        case JULY:
            calendar.set(Calendar.MONTH, 6);
            break;
        case AUGUST:
            calendar.set(Calendar.MONTH, 7);
            break;
        case SEPTEMBER:
            calendar.set(Calendar.MONTH, 8);
            break;
        case OCTOBER:
            calendar.set(Calendar.MONTH, 9);
            break;
        case NOVEMBER:
            calendar.set(Calendar.MONTH, 10);
            break;
        case DECEMBER:
            calendar.set(Calendar.MONTH, 11);
            break;
        default:
            throw new IllegalArgumentException("unknown month: month = " + month);
        }
        calendar.set(Calendar.DATE, day);
        calendar.set(Calendar.HOUR, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, second);
        return calendar.getTime();
    }

    /**
     * Takes the time in a given date and returns how many minutes past from the
     * specified day from 0 : 0 : 0.
     * <p></p>
     * Examples:
     * <ul>
     *   <li>1st January 2012 00 : 00 : 00 => 0</li>
     *   <li>1st January 2012 00 : 00 : 14 => 0</li>
     *   <li>1st January 2012 00 : 59 : 14 => 59</li>
     *   <li>1st January 2012 01 : 00 : 35 => 60</li>
     *   <li>1st January 2012 01 : 12 : 35 => 72</li>
     *   <li>1st January 2012 23 : 59 : 59 => 1439 (23 * 60 + 59)</li>
     * </ul>
     * 
     * @param date input date
     * @return how many minutes past from the noon of the day in the specified timestamp
     */
    public static int getMinutesInDay(Date date) {
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(date);
        return calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE);
    }

    /**
     * Takes the time in a given date and returns how many hours past from the
     * specified day from 0 : 0 : 0.
     * <p></p>
     * Examples:
     * <ul>
     *   <li>1st January 2012 00 : 00 : 00 => 0</li>
     *   <li>1st January 2012 00 : 00 : 14 => 0</li>
     *   <li>1st January 2012 00 : 59 : 14 => 0</li>
     *   <li>1st January 2012 01 : 00 : 35 => 1</li>
     *   <li>1st January 2012 01 : 12 : 35 => 1</li>
     *   <li>1st January 2012 23 : 59 : 59 => 23</li>
     * </ul>
     * 
     * @param date input date
     * @return how many hours past from the noon of the day in the specified timestamp
     */
    public static int getHoursInDay(Date date) {
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(date);
        return calendar.get(Calendar.HOUR_OF_DAY);
    }

    /**
     * Takes the time in a given date and returns how many days past from the beginning of the year.
     * This is value in range <0, 364> for the year which does not have 29 February and value
     * in rage <0, 365> for the year which has 29 February.
     * <p></p>
     * Examples:
     * <ul>
     *   <li>1st January 2011 => 0</li>
     *   <li>31th January 2011 => 30</li>
     *   <li>1st August 2011 => 212</li>
     *   <li>31st December 2011 => 364</li>
     *   <li>1st January 2012 => 0</li>
     *   <li>31th January 2012 => 30</li>
     *   <li>1st August 2012 => 213</li>
     *   <li>31st December 2012 => 365</li>
     * </ul>
     * @param date input date
     * @return how many days past from the beginning of the year
     */
    public static int getDaysInYear(Date date) {
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(date);
        return calendar.get(Calendar.DAY_OF_YEAR) - 1;
    }

    /**
     * Returns the difference in days between two dates. Only the date and more
     * significant fields are included in the calculation. The formula is:
     * numberOfDays(truncToDays(second) - truncToDays(first)).
     * <p>
     * </p>
     * Examples:
     * <ul>
     * <li>(1st January 2012 00 : 00 : 00, 1st January 2012 00 : 00 : 00) => 0</li>
     * <li>(1st January 2012 00 : 00 : 00, 1st January 2012 23 : 59 : 59) => 0</li>
     * <li>(1st January 2012 23 : 59 : 59, 1st January 2012 00 : 00 : 00) => 0</li>
     * <li>(1st January 2012 00 : 00 : 00, 2nd January 2012 00 : 00 : 00) => 1</li>
     * <li>(1st January 2012 23 : 59 : 59, 2nd January 2012 00 : 00 : 00) => 1</li>
     * <li>(1st January 2012 23 : 59 : 59, 2nd January 2012 23 : 59 : 59) => 1</li>
     * <li>(2nd January 2012 00 : 00 : 00, 1st January 2012 23 : 59 : 59) => -1</li>
     * <li>(1st January 2012 00 : 00 : 00, 1st February 2012 00 : 00 : 00) => 31</li>
     * </ul>
     * 
     * @param first first date
     * @param second second date
     * @return the days difference
     */
    public static int getDifferenceInDays(Date first, Date second) {
        Date firstDay = DateUtils.truncate(first, Calendar.DATE);
        Date secondDay = DateUtils.truncate(second, Calendar.DATE);
        return Long.valueOf((secondDay.getTime() - firstDay.getTime()) / 1000 / 60 / 60 / 24).intValue();
    }

    /**
     * Returns the string with the formatted date with the specified pattern.
     * 
     * @param date date which will be formatted
     * @param pattern pattern which will be used
     * @return string with formatted date
     */
    public static String getFormattedDate(Date date, String pattern) {
        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
        return sdf.format(date);
    }
    
    /**
     * Returns the date which is created from the sting and specified format.
     * 
     * @param date date string which will be parsed
     * @param pattern pattern which will be used
     * @return created date
     */
    public static Date getDateFromString(String date, String pattern) {
        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
        try {
            return sdf.parse(date);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * Returns the dates provider.
     * 
     * @return instance of dates provider
     */
    public static DatesProvider getProvider() {
       return provider; 
    }
    
}
